package com.bingxue.edu.parent.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import cn.hutool.json.JSONUtil;
import com.bingxue.edu.common.config.SecurityPolicyProperties;
import com.bingxue.edu.common.enums.CommonStatusEnum;
import com.bingxue.edu.common.enums.UserType;
import com.bingxue.edu.framework.common.constant.CacheConstants;
import com.bingxue.edu.framework.common.util.HttpContextHolder;
import com.bingxue.edu.framework.common.util.IpUtils;
import com.bingxue.edu.framework.common.util.ValidationUtils;
import com.bingxue.edu.framework.redis.RedisUtils;
import com.bingxue.edu.management.auth.service.SocialAccountService;
import com.bingxue.edu.management.education.model.entity.Parent;
import com.bingxue.edu.management.education.service.ParentService;
import com.bingxue.edu.management.system.model.entity.SocialAccount;
import com.bingxue.edu.parent.model.AuthParent;
import com.bingxue.edu.parent.model.resp.ParentInfo;
import com.bingxue.edu.parent.util.ParentAuthUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import me.zhyd.oauth.model.AuthUser;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDateTime;

import static com.bingxue.edu.management.education.model.entity.table.ParentTableDef.PARENT;

/**
 * 学员登录业务实现
 *
 * @author panbo
 * @since 2024/7/27
 */
@Service
@RequiredArgsConstructor
public class ParentAuthService {

    private final ParentService parentService;
    private final SocialAccountService socialAccountService;
    private final SecurityPolicyProperties securityPolicyProperties;

    /**
     * 手机号登录
     *
     * @param phone 手机号
     * @return 令牌
     */
    public String loginByPhone(String phone) {
        Parent parent = parentService.getByPhone(phone);
        ValidationUtils.throwIfNull(parent, "此手机号未注册，请联系工作人员");
        this.checkUserStatus(parent);
        return this.login(parent);
    }

    /**
     * 三方账号登录
     *
     * @param authUser 三方账号信息
     * @return 令牌
     */
    @Transactional(rollbackFor = Exception.class)
    public String socialLogin(AuthUser authUser) {
        String source = authUser.getSource();
        String openId = authUser.getUuid();
        SocialAccount userSocial = socialAccountService.getBySourceAndOpenId(UserType.PARENT, source, openId);
        ValidationUtils.throwIfNull(userSocial, "此社交账号号未绑定本系统账号");
        Parent parent = parentService.getById(userSocial.getUserId());
        ValidationUtils.throwIfNull(parent, "此手机号未绑定本系统账号");
        this.checkUserStatus(parent);
        userSocial.setMetadata(JSONUtil.toJsonStr(authUser));
        userSocial.setLastLoginTime(LocalDateTime.now());
        socialAccountService.saveOrUpdate(userSocial);
        return this.login(parent);
    }

    /**
     * 登录并缓存用户信息
     *
     * @param parent 用户信息
     * @return 令牌
     */
    private String login(Parent parent) {
        // 更新登录信息
        String loginIp = JakartaServletUtil.getClientIP(HttpContextHolder.getRequest());
        parentService.updateChain()
                .set(PARENT.LAST_LOGIN_TIME, LocalDateTime.now())
                .set(PARENT.LAST_LOGIN_IP, loginIp)
                .set(PARENT.LAST_LOGIN_ADDRESS, IpUtils.getIpv4Address(loginIp))
                .where(PARENT.ID.eq(parent.getId()))
                .update();

        AuthParent authParent = buildAuthParent(parent);
        // TODO: 设置当前学生
        return ParentAuthUtil.login(authParent);
    }

    public AuthParent buildAuthParent(Parent parent) {
        ParentInfo parentInfo = BeanUtil.copyProperties(parent, ParentInfo.class);
        AuthParent authParent = new AuthParent();
        authParent.setUserInfo(parentInfo);
        return authParent;
    }

    /**
     * 检查用户状态
     *
     * @param parent 用户信息
     */
    private void checkUserStatus(Parent parent) {
        ValidationUtils.throwIfEqual(CommonStatusEnum.DISABLED, parent.getStatus(), "此账号已被禁用，如有疑问，请联系工作人员");
    }

    /**
     * 检测用户是否已被锁定
     *
     * @param username 用户名
     * @param request  请求对象
     * @param isError  是否登录错误
     */
    private void checkUserLocked(String username, HttpServletRequest request, boolean isError) {
        int maxErrorCount = securityPolicyProperties.getMaxLoginFailedCount();
        // 小于等于0不锁定
        if (maxErrorCount <= 0) {
            return;
        }
        // 检测是否已被锁定
        String key = CacheConstants.USER_PASSWORD_ERROR_KEY_PREFIX + RedisUtils.formatKey(username, JakartaServletUtil.getClientIP(request));
        int lockMinutes = securityPolicyProperties.getLoginFailedLockMinutes();
        int currentErrorCount = ObjectUtil.defaultIfNull(RedisUtils.get(key), 0);
        ValidationUtils.throwIf(currentErrorCount >= maxErrorCount, "账号锁定{}分钟，请稍后再试", lockMinutes);
        // 登录成功清除计数
        if (!isError) {
            RedisUtils.delete(key);
            return;
        }
        // 登录失败递增计数
        currentErrorCount++;
        RedisUtils.set(key, currentErrorCount, Duration.ofMinutes(lockMinutes));
        ValidationUtils.throwIf(currentErrorCount >= maxErrorCount, "登录失败已达{}次，账号锁定{}分钟", maxErrorCount, lockMinutes);
    }
}
